package com.basin.common.satoken.config;

import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.basin.api.vo.UriAuthConfigVO;
import com.basin.common.core.exception.BasinException;
import com.basin.common.core.factory.YmlPropertySourceFactory;
import com.basin.common.core.pojo.BasinNode;
import com.basin.common.core.response.Resp;
import com.basin.common.satoken.annotation.IgnoreGlobalAuth;
import com.basin.common.satoken.source.BasinAuthDataSourceReader;
import com.basin.common.satoken.support.BasinAuthTool;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;


/**
 * @author: Sam ZHONG | sammy1997@aliyun.com
 */
@Slf4j
@Configuration
@Import({BasinAuthDataSourceReader.class})
@PropertySource(value = "classpath:basin-common-satoken.yml", factory = YmlPropertySourceFactory.class)
public class SaTokenConfig {
    @Resource
    private BasinAuthDataSourceReader basinAuthDataSourceReader;

    @Resource(name = "requestMappingHandlerMapping")
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Value("${spring.application.name}")
    private String appName;

    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }

    @Bean
    @Conditional(value = InjectAuthServletFilter.class)
    public SaServletFilter getSaServletFilter() {
        log.info("~ Initializing BasinAuth ServletFilter Successfully ~");
        return new SaServletFilter()
                .addInclude("/**")
                .addExclude("/static/**", "/favicon.ico", "/swagger-ui/**", "/v3/api-docs/**")
                .setAuth(handler -> {
                    // uri的自定义扩展
                    String requestPath = "/%s%s".formatted(appName, SaHolder.getRequest().getRequestPath());
                    // 可能是没注册进管理面 需要扫描看是否存在路径
                    ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                    assert attrs != null;
                    HttpServletRequest request = attrs.getRequest();
                    HandlerExecutionChain handlerExecutionChain;
                    try {
                        handlerExecutionChain = requestMappingHandlerMapping.getHandler(request);
                    } catch (Exception e) {
                        log.error("获取RequestMapping资源异常", e);
                        throw new BasinException("获取RequestMapping资源异常：%s".formatted(e.getMessage()));
                    }
                    if (handlerExecutionChain == null) {
                        SaHolder.getResponse().setStatus(HttpStatus.SC_NOT_FOUND);
                        throw new BasinException("访问的资源不存在");
                    }
                    if (handlerExecutionChain.getHandler() instanceof HandlerMethod handlerMethod) {
                        IgnoreGlobalAuth ignoreGlobalAuth = handlerMethod.getMethod().getAnnotation(IgnoreGlobalAuth.class);
                        if (ignoreGlobalAuth == null) {
                            ignoreGlobalAuth = handlerMethod.getBeanType().getAnnotation(IgnoreGlobalAuth.class);
                        }
                        if (ignoreGlobalAuth != null) {
                            Class<? extends SaFilterAuthStrategy> selfAuthStrategy = ignoreGlobalAuth.authStrategy();
                            try {
                                Method method = Arrays.stream(selfAuthStrategy.getDeclaredMethods())
                                        .filter(m -> m.getName().equals("run")).findFirst().orElse(null);
                                if (ObjUtil.isNull(method)) return;
                                method.setAccessible(true);
                                method.invoke(selfAuthStrategy.getDeclaredConstructor().newInstance(), requestPath);
                            } catch (InstantiationException | NoSuchMethodException | IllegalAccessException |
                                     InvocationTargetException ex) {
                                log.error("反射执行自定义鉴权过滤错误={}", ex.getMessage(), ex);
                                throw new BasinException(HttpStatus.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
                            }
                            return;
                        }
                    }
                    // 白名单uri
                    List<String> whiteUris = basinAuthDataSourceReader.getWhiteUris();
                    if (whiteUris.contains(requestPath)) {
                        log.info("白名单uri={}, 无需鉴权", requestPath);
                        return;
                    }
                    // 白名单ip
                    List<String> whiteIps = basinAuthDataSourceReader.getWhiteIps();
                    String host = SaHolder.getRequest().getHost();
                    if (whiteIps.contains(host)) {
                        log.info("白名单ip={}, 无需鉴权", host);
                        return;
                    }
                    // 登录校验
                    if (!StpUtil.isLogin()) {
                        SaHolder.getResponse().setStatus(HttpStatus.SC_UNAUTHORIZED);
                        throw new BasinException("用户未登录");
                    }
                    String loginId = BasinAuthTool.getLoginId();
                    // 白名单user
                    List<String> whiteUserIds = basinAuthDataSourceReader.getWhiteUserIds();
                    if (whiteUserIds.contains(loginId)) {
                        log.info("白名单userId={}, 无需鉴权", loginId);
                        return;
                    }
                    // 路由 -> 角色校验 & 权限校验
                    UriAuthConfigVO uriAuthConfig = basinAuthDataSourceReader.getUriAuthConfig(requestPath);
                    if (ObjUtil.isNull(uriAuthConfig)) {
                        // 没有配置的路径
                        return;
                    }
                    validateAuths(uriAuthConfig, true);
                    validateAuths(uriAuthConfig, false);
                })
                .setBeforeAuth(r -> {
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer(appName)
                            // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                    ;
                })
                .setError(err -> Resp.fail(err.getMessage()))
                ;
    }

    private static void validateAuths(UriAuthConfigVO uriAuthConfig, boolean isRoleDimension) {
        Boolean roleCheck = null;
        Queue<String> nextModeList = new LinkedList<>();
        BasinNode<UriAuthConfigVO.AuthCondition> node = isRoleDimension ? uriAuthConfig.getRoleCondition() : uriAuthConfig.getPermCondition();
        while (node != null) {
            UriAuthConfigVO.AuthCondition authCondition = node.data;
            nextModeList.add(authCondition.getNextMode());
            List<String> dataList = authCondition.getDataList();
            boolean isHit = true;
            List<String> userAuthList = isRoleDimension ? StpUtil.getRoleList() : StpUtil.getPermissionList();
            if (SaMode.AND.name().equalsIgnoreCase(authCondition.getInnerMode())) {
                isHit = CollUtil.containsAll(userAuthList, dataList);
            }
            if (SaMode.OR.name().equalsIgnoreCase(authCondition.getInnerMode())) {
                isHit = CollUtil.containsAny(userAuthList, dataList);
            }
            // 第一个节点
            if (ObjUtil.isNull(roleCheck)) {
                roleCheck = isHit;
            } else {
                roleCheck = SaMode.AND.name().equalsIgnoreCase(nextModeList.poll())
                        ? roleCheck && isHit : roleCheck || isHit;
            }
            // 读取下一个节点
            if (ObjUtil.isNotNull(node)) node = node.next;
        }
        if (ObjUtil.isNotNull(roleCheck) && !roleCheck) {
            throw new BasinException("%s权限校验未通过".formatted(isRoleDimension ? "role" : "perm"));
        }
    }

    static class InjectAuthServletFilter implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            // 认证服务不开
            String appName = context.getEnvironment().getProperty("spring.application.name");
            return !"basin-auth".equals(appName);
        }
    }
}
